package org.reborn.engine.model.mesh

import org.reborn.math.VectorF
import org.reborn.math.util.MathUtils
import org.reborn.engine.enums.ArrayType._
import org.reborn.utils.ListUtil
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.HashMap

class MeshConnectivity() {
  var vertices:ArrayBuffer[ConnectedVertex] = null;
  var edges:ArrayBuffer[ConnectedEdge] = null;
  var faces:ArrayBuffer[ConnectedFace] = null;
  
  def verifyConnectivity():Option[String] = {
	def invalidMesh(message:String):Option[String] = {
		return Some("INVALID MESH - " + message);
	}

	var foundFaces:Set[ConnectedFace] = Set()
	var foundEdges:Set[ConnectedEdge] = Set()
	for(vert <- this.vertices){
	  for(face <- vert.getFaces){
	 	  foundFaces += face
	 	  if(face.getVertices.size != 3)
	 	 	  return invalidMesh("Not the right amount of faces in vertex.")
	  if(face.getVertices.distinct.size != 3)
	 	  return invalidMesh("Face contains duplicate vertices.")
	  var containsVert = false; 
	  for(faceVert <- face.getVertices){
	 	  if(faceVert eq vert)
	 	 	  containsVert = true;
	 	  if(!this.vertices.contains(faceVert))
	 	 	  return invalidMesh("Face contains a vertex that is not in the list of vertices.")
	  }
	  if(!containsVert)
	 	  return invalidMesh("Face does not contain the vertex that references it.")
	  }
	  for(edge <- vert.getEdges){
	 	  foundEdges += edge
	 	  if(edge.getVertices.distinct.size != 2)
	 	 	  return invalidMesh("Edge contains duplicate vertices.")
	  var containsVert = false;
	  for(edgeVert <- edge.getVertices){
	 	  if(edgeVert eq vert)
	 	 	  containsVert = true;
	 	  if(!this.vertices.contains(edgeVert))
	 	 	  return invalidMesh("Edge contains a vertex that is not in the list of vertices.")
	  }
	  if(!containsVert)
	 	  return invalidMesh("Edge does not contain the vertex that references it.")
	 	  }
	  }
	  if(foundFaces.size != this.faces.size)
	 	  return invalidMesh("Number of referenced Faces in vertex list is different from MeshCon Face list")
	  if(foundEdges.size != this.edges.size)
	 	  return invalidMesh("Number of referenced Edges in vertex list is different from MeshCon Edge list")
	 	  
	  return None;
	}
}

trait ConnectedMeshComponent


class ConnectedVertex() extends ConnectedMeshComponent{
  val dataMap:HashMap[ArrayType,VectorF] = new HashMap();
  private var edges:ArrayBuffer[ConnectedEdge] = new ArrayBuffer(4);
  private var faces:ArrayBuffer[ConnectedFace] = new ArrayBuffer(4);
  
  def addEdge(edge:ConnectedEdge):ConnectedVertex = {edges += edge; this;}
  def addEdgeIfUnique(edge:ConnectedEdge):ConnectedVertex = {
	if(!containsEdge(edge)) 
	  edges += edge;
    this;
  }
  def removeEdge(edge:ConnectedEdge):ConnectedVertex = {edges -= edge; this;}
  def removeEdge(index:Int):ConnectedVertex = {edges.remove(index);this;}
  def removeEdgeReference(edge:ConnectedEdge):ConnectedVertex = {edges = edges.filter((curEdge) => !(edge eq curEdge)); this;}
  def getEdges():ArrayBuffer[ConnectedEdge] = edges
  def getEdge(index:Int):ConnectedEdge = edges(index);
  def getEdgeIndex(edge:ConnectedEdge):Int = edges.indexOf(edge)
  def containsEdge(edge:ConnectedEdge):Boolean = { edges.exists(_ == edge)}
  def numEdges():Int = edges.size;
  def removeDuplicateEdges():ConnectedVertex = {edges = edges.distinct; this;}
  
  def addFace(face:ConnectedFace):ConnectedVertex = {faces += face; this;}
  def addFaceIfUnique(face:ConnectedFace):ConnectedVertex = {
	if(!faces.exists(_ eq face)) 
	  faces += face;
    this;
  }
  def removeFace(face:ConnectedFace):ConnectedVertex = {faces -= face; this;}
  def removeFace(index:Int):ConnectedVertex = {faces.remove(index); this;}
  def getFaces():ArrayBuffer[ConnectedFace] = faces
  def getFace(index:Int):ConnectedFace = faces(index);
  def getFaceIndex(face:ConnectedFace):Int = faces.indexOf(face)
  def numFaces():Int = faces.size;
  def removeDuplicateFaces():ConnectedVertex = {faces = faces.distinct; this;}
  
}

class ConnectedEdge(var v1:ConnectedVertex,var v2:ConnectedVertex,var weight:Float = 0.0f) extends ConnectedMeshComponent{

  def containsVertex(vert:ConnectedVertex):Boolean = (v1 == vert || v2 == vert)
  
  def replaceVertex(existingVert:ConnectedVertex,newVert:ConnectedVertex):ConnectedEdge = {
	if(v1 == existingVert) {v1 = newVert}
	else if(v2 == existingVert) {v2 = newVert}
	else
		println("Did not replace vertex.");
	this
  }
  
  def otherVertex(vert:ConnectedVertex):ConnectedVertex = {
	if(v1 == vert) v2 
	else if(v2 == vert) v1
	else throw new IllegalArgumentException("vert must be a vertex attached to the edge") 	  
  }
  
  def getVertices():List[ConnectedVertex] = {List(v1,v2)}
  
  /**
   * @return all the faces that are connected to both vertices of this edge
   */
  def connectedFaces():List[ConnectedFace] = {
	v1.getFaces.filter(_.containsVertex(v2)).toList
  }
  
  /**
   * @return all edges connected to v1, excluding this one
   */
  def connectedEdgesV1():List[ConnectedEdge] = {
	v1.getEdges.toList.filterNot(_ == this)
  }
  
   /**
   * @return all edges connected to v2, excluding this one
   */
  def connectedEdgesV2():List[ConnectedEdge] = {
	v2.getEdges.toList.filterNot(_ == this)
  }
  
  /**
  *	2 Connected Edges are equal if they contain the same 2 vertices.
  */
  override def equals(o:Any):Boolean = {
	o match{
		case edge:ConnectedEdge => (v1 == edge.v1 && v2 == edge.v2) || (v1 == edge.v2 && v2 == edge.v1)
		case _ => false;
	}
  }
  
  override def hashCode():Int = v1.hashCode * v2.hashCode
}

class ConnectedFace() extends ConnectedMeshComponent{
  private var verts:ArrayBuffer[ConnectedVertex] = new ArrayBuffer(4)
  
  def addVertex(vert:ConnectedVertex):ConnectedFace = {verts += vert; this;}
  def removeVertex(vert:ConnectedVertex):ConnectedFace = {verts += vert; this;}
  def removeVertex(index:Int):ConnectedFace = {verts.remove(index); this;}
  def getVertices():ArrayBuffer[ConnectedVertex] = verts
  def getVertex(index:Int):ConnectedVertex = verts(index);
  def getVertexIndex(vert:ConnectedVertex):Int = verts.indexOf(vert)
  def containsVertex(vert:ConnectedVertex):Boolean = verts.contains(vert)
  def numVertices():Int = verts.size;
  
  def replaceVertex(existingVert:ConnectedVertex,newVert:ConnectedVertex):ConnectedFace = {
	val ind = verts.indexOf(existingVert)
	if(ind >= 0)
	  verts(ind) = newVert
	else
	  println("Did not replace vertex.")
	this
  }
  
  def connectedEdges():List[ConnectedEdge] = {
	var edgeList:List[ConnectedEdge] = Nil
	val numVerts = verts.size
	for(i <- 0 until numVerts){
	  val v1 = verts(i)
	  val v2 = verts(MathUtils.wrappedIndex(i+1,numVerts))
	  
	  for(edge <- v1.getEdges; if(edge.containsVertex(v2)))
	 	edgeList = edgeList ::: List(edge)
	}
	
	edgeList
  }
  
  def connectedFaces():List[ConnectedFace] = {
	var faceList:List[ConnectedFace] = Nil
	val numVerts = verts.size
	for(i <- 0 until numVerts){
	  val v1 = verts(i)
	  val v2 = verts(MathUtils.wrappedIndex(i+1,numVerts))
	  
	  for(face <- v1.getFaces; if(face.containsVertex(v2)))
	 	faceList = faceList ::: List(face)
	}
	
	faceList
  }
  
  def findEdge(v1:ConnectedVertex,v2:ConnectedVertex):Option[ConnectedEdge] = {
	val search = new ConnectedEdge(v1,v2)
	v1.getEdges().find(_ == search)
  }
  
}